﻿#region License Info

//Component of Cronos Package, http://www.codeplex.com/cronos
//Copyright (C) 2009 Anthony Brockwell

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using ABMath;
using ABMath.ModelFramework;
using ABMath.ModelFramework.Data;
using ABMath.ModelFramework.Models;
using ABMath.ModelFramework.Visuals;
using ExtraControlLibrary.DirectedGraphs;
using ExtraControlLibrary.Forms;
using MathNet.Numerics.LinearAlgebra;

namespace ExtraControlLibrary.VisualControls
{
    public partial class ModelControl : UserControl
    {
        private readonly DirectedGraphViewer dagViewer;
        private readonly IShell shell;
        private readonly Model theModel;
        private ProgressForm pform;

        private int LDiterations = 250;
        private int NMiterations = 250;
        private double consistencyPenalty = 0;

        public ModelControl(Model theModel, DirectedGraphViewer dagViewer, IShell parent, bool showLag0)
        {
            InitializeComponent();
            this.dagViewer = dagViewer;
            this.theModel = theModel;
            shell = parent;
            ShowLag0 = showLag0;
            modelACF.ShowLag0 = showLag0;
            modelPACF.ShowLag0 = showLag0;
            plot1.ShowLag0 = showLag0;
            var ief = theModel as IExtraFunctionality;
            if (ief != null)
               extraFunctionPanel1.FunctionObject = ief;
            else
               extraFunctionPanel1.Hide();

            // these are not used for now
            aicLabel.Hide();
            aicTextBox.Hide();

            UpdateDisplay();
        }

        protected bool ShowLag0 { get; set; }

        private void UpdateDisplay()
        {
            int n = 0;
            richTextBox1.Text = theModel.GetDescription();
            if (theModel.TheData != null)
            {
                var ts = theModel.TheData as TimeSeries;
                if (ts != null)
                {
                    richTextBox2.Text = ts.GetDescription();
                    n = ts.Count;
                }
            }
            else
                richTextBox2.Text = "No data connection.";

            var rts = theModel.Residuals as TimeSeries;
            if (rts != null)
                plot1.SetVisual(new TSPlotVisual(rts));
            var mts = theModel.Residuals as MVTimeSeries;
            if (mts != null)
                plot1.SetVisual(new TSPlotVisual(mts));

            llTextBox.Text = string.Format("{0:0.0000}", theModel.GoodnessOfFit);

            var tsm = theModel as UnivariateTimeSeriesModel;
            if (tsm != null)
            {
                var lags = new int[41];
                for (int i = 0; i <= 40; ++i)
                    lags[i] = i;
                Vector acf = tsm.ComputeACF(40, true);
                modelACF.SetACF(lags, acf, n);
                modelACF.Refresh();
                Vector pacf = TimeSeries.GetPACFFrom(acf);
                modelPACF.SetACF(lags, pacf, n);
                modelPACF.Refresh();
            }
        }

        private void MLEFitIteration(Vector param, double functionValue, int percentComplete, bool finished)
        {
            backgroundMLE.ReportProgress(percentComplete);
        }

        private void mleFitButton_Click(object sender, EventArgs e)
        {
            if (theModel.TheData == null)
            {
                MessageBox.Show("No data has been connected to the model.", "Cannot Fit");
                return;
            }
            if (theModel.CanUseMLE())
            {
                var mleForm = new MLEForm(LDiterations, NMiterations, consistencyPenalty) 
                   { StartPosition = FormStartPosition.CenterScreen };
                if (mleForm.ShowDialog() == DialogResult.OK)
                {
                    LDiterations = mleForm.LDIterations;
                    NMiterations = mleForm.NMIterations;
                    consistencyPenalty = mleForm.ConsistencyPenalty;
                    pform = new ProgressForm("Fitting model by maximum likelihood ...")
                                {StartPosition = FormStartPosition.CenterScreen};
                    pform.Show();
                    backgroundMLE.RunWorkerAsync();
                }
            }
            else if (theModel.CanUseMoM())
            {
                theModel.FitByMoM(); // MoM fitting is quick so doesn't need a dialog box
                UpdateDisplay();
                dagViewer.UpdateFromNodeContaining(theModel);
                Refresh();
            }
            else
                MessageBox.Show("No model fitting methods have been implemented for this class of models.", "Error");
        }

        private void backgroundMLE_DoWork(object sender, DoWorkEventArgs e)
        {
            if (theModel.TheData != null)
                theModel.FitByMLE(LDiterations, NMiterations, consistencyPenalty, MLEFitIteration);
        }

        private void backgroundMLE_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            pform.Close();
            UpdateDisplay();
            dagViewer.UpdateFromNodeContaining(theModel);
            Refresh();
            pform = null;
        }

        private void backgroundMLE_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            pform.PercentComplete = e.ProgressPercentage;
        }

        private void parameterButton_Click(object sender, EventArgs e)
        {
            Vector p = theModel.Parameters;
            var pnames = new List<string>();
            var pvals = new List<double>();
            var pmodes = new List<Model.ParameterState>();
            for (int i = 0; i < p.Length; ++i)
            {
                pnames.Add(theModel.GetParameterName(i));
                pvals.Add(p[i]);
                pmodes.Add(theModel.ParameterStates[i]);
            }

            var peditor = new ParameterEditor(pnames, pvals, pmodes) {StartPosition = FormStartPosition.CenterParent};

            if (peditor.ShowDialog() == DialogResult.OK)
            {
                // get vector of results
                Vector newParms = peditor.GetResultVector();
                if (!theModel.SetParameters(newParms))
                    MessageBox.Show("Invalid parameter specification, parameters not set.", "Warning");
                else
                {
                    theModel.ParameterStates = peditor.GetResultStates();
                    theModel.LogLikelihood(null, 0.0, true);
                    UpdateDisplay();
                    // now make outputs cascade out ...
                    dagViewer.UpdateFromNodeContaining(theModel);
                }
            }
        }

        private void simulateButton_Click(object sender, EventArgs e)
        {
            var times = new List<DateTime>();

            // pick up datetimes from the current data
            var ts = theModel.TheData as TimeSeries;

            var drs = new DateRangeSelectForm(ts) {StartPosition = FormStartPosition.CenterParent};
            if (drs.ShowDialog() == DialogResult.OK)
            {
                times = drs.GetTimeList();

                var randomSeed = (int) (DateTime.Now.Ticks%int.MaxValue);
                object simulation = theModel.SimulateData(times, randomSeed);
                var simulated = simulation as TimeSeries;
                var MVsimulated = simulation as MVTimeSeries;
                if (simulated == null && MVsimulated == null)
                {
                    MessageBox.Show(
                        "Simulation failed, either simulation not supported for this model or the range of date/times was invalid.",
                        "Error");
                    return;
                }

                // otherwise we can drop it into the workspace
                MessageBox.Show("Time series simulated, you can now drop it into the workspace.", "Simulation");

                if (simulated != null)
                    shell.DropIntoWorkspace(simulated);
                if (MVsimulated != null)
                    shell.DropIntoWorkspace(MVsimulated);
            }
        }

        private void extraFunctionPanel1_FunctionCompleted(object sender, ExtraFunctionPanel.ExtraFunctionArgs args)
        {
            if (args.result != null) // then it as an object to be dropped
                shell.DropIntoWorkspace(args.result);
        }
    }
}
